# -*- coding: utf-8 -*-

#    OriginDB
#    Copyright (C) 2010  Jason Power <power.jg@gmail.com>
#    Copyright (C) 2010  Travis Gockel <travis@gockelhut.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import logging

from google.appengine.ext import db
import yaml

from mymodel import MyModel, MySearchableModel, NotUniqueError
from search import Searchable


class User(MySearchableModel):
	username = db.StringProperty() #unique
	name = db.StringProperty()
	email = db.StringProperty() #unique
	institution = db.StringProperty()
	password = db.StringProperty()
	currentSID = db.StringProperty() #unique

	privilege = db.StringProperty(required=True, default="normal", choices=["normal", "admin"])

	modifiedTime = db.DateTimeProperty(required=True, auto_now=True)

	_unique = set(['username', 'email', 'currentSID'])
	INDEX_ONLY = ['username', 'name', 'email', 'institution']
	INDEX_TITLE_FROM_PROP = 'name'
	INSERT_REQUIRE_GROUP = set(['admin'])
	
	@classmethod
	def search(cls, yml):
		return cls._search(yml)
	
	def __str__(self):
		return "%s: %s (%s)" % (self.username, self.name, self.email)

class Compound(MySearchableModel):
	name = db.StringProperty() #required....  #unique
	formula = db.StringProperty()
	IUPACname = db.StringProperty() #unique
	inchi = db.StringProperty()
	molfile = db.TextProperty()
	fingerprint = db.ListProperty(int)
	
	user = db.ReferenceProperty(User) #required....
	modifiedTime = db.DateTimeProperty(required=True, auto_now=True)

	_unique = set(['name', 'IUPACname'])
	INDEX_ONLY = ['name', 'formula', 'IUPACname', 'inchi'];
	_searchable = INDEX_ONLY
	INDEX_TITLE_FROM_PROP = 'name'

class Reference(MySearchableModel):
	title = db.StringProperty() #required #unique
	authors = db.StringListProperty() #required
	source = db.StringProperty()
	year = db.IntegerProperty()
	volume = db.IntegerProperty()
	issue = db.IntegerProperty()
	pageStart = db.IntegerProperty()
	pageEnd = db.IntegerProperty()
	abstract = db.TextProperty()
	
	user = db.ReferenceProperty(User) #required....
	modifiedTime = db.DateTimeProperty(required=True, auto_now=True)
	
	_unique = set(['title'])
	_searchable = set(['title', 'authors', 'source', 'year', 'volume', 'issue', 'page_end', 'page_start'])
	NON_INDEXED_SEARCHABLE = set(['source', 'year', 'volume', 'issue', 'page_end', 'page_start', 'abstract'])
	INDEX_ONLY = ['title', 'authors']
	INDEX_TITLE_FROM_PROP = 'title'
	
	@classmethod
	def insert(cls, yml, user):
		result = cls._insert(yml, user)
		result.enqueue_indexing(url='/tasks/searchindexing')
		return result;

class Reaction(MyModel):
	reference = db.ReferenceProperty(Reference, collection_name='reactions') # required...
	temperature = db.IntegerProperty()
	solvent = db.StringProperty()
	liquid = db.BooleanProperty()
	solid = db.BooleanProperty()
	gas = db.BooleanProperty()
	time = db.FloatProperty()
	pressure = db.FloatProperty()
	pH = db.FloatProperty()
	other = db.TextProperty()
	catalysts = db.ListProperty(db.Key)
	
	user = db.ReferenceProperty(User) #required....
	modifiedTime = db.DateTimeProperty(required=True, auto_now=True)

	def dump(self):
		other = {}
		other['catalysts'] = [str(i) for i in self.catalysts]
		prods = {}
		for i in self.products:
			prods[str(i.compound.key())] = i.percent_yield
		reacts = {}
		for i in self.reactants:
			reacts[str(i.compound.key())] = i.mol_percent
		other['products'] = prods
		other['reactants'] = reacts
		if self.reference:
			other['reference'] = str(self.reference.key())
		return MyModel.dump(self, other)

	@classmethod
	def insert(cls, yml, user):
		data = yaml.load(yml)
		dataCopy = data.copy()

		toDel = set(data.keys()) & set(["reactants", "products", "reference", "catalysts"])
		for i in toDel:
			del data[i]

		obj = cls._insert(data, user)
		data = dataCopy
		logging.info("all data: "+str(data))
		keys = data.keys()
		if "reactants" in keys:
			reactantDict = data["reactants"]
			for k in reactantDict:
				logging.info(k)
				compound = Compound.get(k)
				reactant = Reactant(compound=compound, reaction=obj, mol_percent=reactantDict[k], user=user)
				reactant.put()
		if "products" in keys:
			productDict = data["products"]
			for k in productDict:
				logging.info(k)
				compound = Compound.get(k)
				product = Product(compound=compound, reaction=obj, percent_yield=productDict[k], user=user)
				product.put()
		if "reference" in keys and data["reference"]:
			obj.reference = Reference.get(data["reference"])
		if "catalysts" in keys:
			for key in data["catalysts"]:
				obj.catalysts.append(db.Key(key))
		obj.put()
		return obj

	def update(self, yml, user):
		data = yaml.load(yml)

		if "reactants" in data.keys():
			cur = set([str(i.compound.key()) for i in self.reactants])
			logging.info("Current: "+str(cur))
			new = set(data["reactants"].keys())
			logging.info("New: "+str(new))
			toRemove = cur - new
			logging.info("To remove: "+str(toRemove))
			toAdd = new - cur
			logging.info("To add: "+str(toAdd))
			for compoundKey in toRemove:
				reactant = Reactant.gql("WHERE compound = :1 AND reaction = :2", compoundKey, self.key()).get()
				logging.info("Deleting Reactant: "+reactant.compound.name)
				reactant.delete()
			for compoundKey in toAdd:
				compound = Compound.get(compoundKey)
				reactant = Reactant(compound=compound, reaction=self, mol_percent=data["reactants"][compoundKey], user=user)
				reactant.put()
			del data["reactants"]
			
		if "products" in data.keys():
			cur = set([str(i.compound.key()) for i in self.products])
			new = set(data["products"].keys())
			toRemove = cur - new
			toAdd = new - cur
			for compoundKey in toRemove:
				product = Product.gql("WHERE compound = :1 AND reaction = :2", compoundKey, self.key()).get()
				logging.info("Deleting Product: "+product.compound.name)
				product.delete()
			for compoundKey in toAdd:
				compound = Compound.get(compoundKey)
				product = Product(compound=compound, reaction=self, percent_yield=data["products"][compoundKey], user=user)
				product.put()
			del data["products"]

		if "reference" in data.keys() and data["reference"]:
			self.reference = Reference.get(data["reference"])
			del data["reference"]
		
		if "catalysts" in data.keys():
			cur = set([str(i) for i in self.catalysts])
			new = set(data["catalysts"])
			toRemove = cur - new
			toAdd = new - cur
			for catKey in toRemove:
				self.catalyst.remove(db.Key(catKey))
			for catKey in toAdd:
				self.catalysts.append(db.Key(catKey))
			del data["catalysts"]

		MyModel.update(self, data, user)
	
	_searchable = set()


class Reactant(MyModel):
	reaction = db.ReferenceProperty(Reaction, required=True, collection_name='reactants')
	compound = db.ReferenceProperty(Compound, required=True, collection_name='compound_reactants')
	mol_percent = db.FloatProperty(default=0)

	user = db.ReferenceProperty(User) #required....
	modifiedTime = db.DateTimeProperty(required=True, auto_now=True)


class Product(MyModel):
	reaction = db.ReferenceProperty(Reaction, required=True, collection_name='products')
	compound = db.ReferenceProperty(Compound, required=True, collection_name='compound_products')
	percent_yield = db.FloatProperty(default=0)

	user = db.ReferenceProperty(User) #required....
	modifiedTime = db.DateTimeProperty(required=True, auto_now=True)


class Catalyst(MySearchableModel):
	name = db.StringProperty() #required....  #unique

	user = db.ReferenceProperty(User) #required....
	modifiedTime = db.DateTimeProperty(required=True, auto_now=True)

	_unique = set(['name'])
	_searchable = set(['name'])
	INDEX_TITLE_FROM_PROP = 'name'

class Pathway(MySearchableModel):
	title = db.StringProperty()  #required... #unique with respect to user
	description = db.TextProperty()

	user = db.ReferenceProperty(User) #required....
	modifiedTime = db.DateTimeProperty(required=True, auto_now=True)

	def dump(self):
		graph = []

		for relation in self.relations:
			graph.append([str(relation.previous.key()),
						 (str(relation.next.key()),str(relation.via.key()))
						 ])
		
		other['graph'] = graph
		
		return MyModel.dump(self, other)

	@classmethod
	def insert(cls, yml, user):
		data = yaml.load(yml)
		dataCopy = data.copy()

		toDel = set(data.keys()) & set(["graph"])
		for i in toDel:
			del data[i]

		obj = cls._insert(data, user)
		data = dataCopy
		logging.info("all data: "+str(data))
		keys = data.keys()
		if "graph" in keys:
			for relation in data["graph"]:
				previous = Reaction.get(relation[0])
				next = Reaction.get(relation[1][0])
				compound = Compound.get(relation[1][1])
				if not compound:
					equCompound = Reactant.get(relation[1][1])
					if not equCompound:
						equCompound = Reactant.get(relation[1][1])
					compound = equCompound.compound

				rrel = ReactionRelation(pathway=obj, previous=previous, next=next, via=compound, user=user)
				rrel.put()

		obj.put()
		return obj

	def update(self, yml, user):
		data = yaml.load(yml)

		if "graph" in data:
			for relation in self.relations:
				relation.delete()
				
			for relation in data["graph"]:
				previous = Reaction.get(relation[0])
				next = Reaction.get(relation[1][0])
				compound = Compound.get(relation[1][1])
				if not compound:
					equCompound = Reactant.get(relation[1][1])
					if not equCompound:
						equCompound = Reactant.get(relation[1][1])
					compound = equCompound.compound

				rrel = ReactionRelation(pathway=obj, previous=previous, next=next, via=compound, user=user)
				rrel.put()

			del data["graph"]

		MyModel.update(self, data, user)
	
	INDEX_TITLE_FROM_PROP = 'title'

class ReactionRelation(MyModel):
	pathway = db.ReferenceProperty(Pathway, required=True, collection_name='relations')
	
	previous = db.ReferenceProperty(Reaction, required=True, collection_name='previouses')
	next = db.ReferenceProperty(Reaction, required=True, collection_name='nexts')

	via = db.ReferenceProperty(Compound, required=True, collection_name='vias')

	user = db.ReferenceProperty(User) #required....
	modifiedTime = db.DateTimeProperty(required=True, auto_now=True)


types = {"User":User,
		 "Compound":Compound,
		 "Reaction":Reaction,
		 "Catalyst":Catalyst,
		 "Reference":Reference,
		 "Reactant":Reactant,
		 "Product":Product,
		 "Pathway":Pathway
		 }